﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using LibAutoBind.Nodes;
using System.Text.RegularExpressions;

namespace LibAutoBind.Transformers
{
    class LuaTransformer : Transformer
    {
        private int m_HeaderLineNumber = 1;
        private int m_CodeLineNumber = 1;

        public LuaTransformer(Machine m)
        {
            this.m_M = m;
        }

        public override void WriteHeaderFile(List<Node> nodes)
        {
            this.ValidityCheck(nodes);

            this.WriteHeaderLine(@"/*
 * This file was automatically generated by AutoBind.
 * Any changes made to this file will be lost when it
 * is next regenerated.
 */");
            this.WriteHeaderLine();
            
            // Get the class node.
            ClassDefinitionNode cls = (ClassDefinitionNode)(this.GetNodesOfType(nodes, typeof(ClassDefinitionNode))[0]);
            if (cls.Alias == "")
                Console.Write(cls.Class);
            else
                Console.Write(cls.Alias);
            this.WriteHeaderLine("#ifndef CLASS_" + cls.Class.Replace('.', '_'));
            this.WriteHeaderLine("#define CLASS_" + cls.Class.Replace('.', '_'));
            this.WriteHeaderLine();

            // Get all of the import nodes and convert them to
            // #includes.  We also predeclare any of the classes
            // we are importing since it's not feasable to detect
            // what classes reference what other classes.
            List<Node> imports = this.GetNodesOfType(nodes, typeof(ImportNode));
            this.WriteHeaderLine("/* Imports */");
            this.WriteHeaderLine("class RObject;");
            foreach (Node n in imports)
            {
                string[] components = n.Content.Split('.');
                for (int i = 0; i < components.Length; i++)
                {
                    if (i != components.Length - 1)
                        this.WriteHeader("namespace " + components[i] + " { ");
                    else
                        this.WriteHeader("class " + components[i] + "; ");
                }
                for (int i = 0; i < components.Length- 1; i++)
                {
                    this.WriteHeader("} ");
                }
                this.WriteHeaderLine();
            }
            this.WriteHeaderLine("#include \"RObject.h\"");
            foreach (Node n in imports)
            {
                if (n.Content == cls.Inheritance)
                    this.WriteHeaderLine("#include \"" + ClassName.ResolveToHeaderFilename(n.Content.Trim()) + "\"");
            }
            foreach (Node n in this.GetNodesOfType(nodes, typeof(IncludeNode)))
            {
                this.WriteHeaderLine("#include " + n.Content.Trim() + "");
            }
            this.WriteHeaderLine();

            // Now add all of the using declarations.
            this.WriteHeaderLine("/* Using declarations */");
            foreach (Node n in this.GetNodesOfType(nodes, typeof(UsingNode)))
            {
                this.WriteHeaderLine("using " + n.Content.Replace(".", "::") + ";");
            }
            this.WriteHeaderLine();

            // Begin declaring the class.
            string[] clscomponents = cls.Class.Split('.');
            for (int i = 0; i < clscomponents.Length; i++)
            {
                if (i < clscomponents.Length - 2)
                    this.WriteHeader("namespace " + clscomponents[i] + " { ");
                else if (i == clscomponents.Length - 2)
                    this.WriteHeader("namespace " + clscomponents[i]);
                else
                {
                    this.WriteHeaderLine();
                    this.WriteHeaderLine("{");
                    this.WriteHeaderLine("    /* Begin class declaration */");
                    this.WriteHeaderLine("    class " + clscomponents[i] + " : public " + cls.Inheritance.Replace(".", "::"));
                    this.WriteHeaderLine("    {");
                }
            }
            
            // Declare all of the variables within the class.
            this.WriteHeaderLine("        /* Variable declarations */");
            foreach (ClassVariableDeclarationNode n in this.GetNodesOfType(nodes, typeof(ClassVariableDeclarationNode)))
            {
                string keys = "";
                string visibility = "private";
                foreach (string k in n.CPPKeywords)
                {
                    if (Keywords.CPPVisibilityKeywords.Contains(k))
                        visibility = k;
                    else
                        keys += k + " ";
                }
                this.WriteHeaderLine("        " + visibility + ": " + keys + n.Type + " " + n.Name + ";");
            }
            this.WriteHeaderLine();

            // Search for all methods used by properties.
            List<string> propertyMethods = new List<string>();
            foreach (ClassPropertyDeclarationNode n in this.GetNodesOfType(nodes, typeof(ClassPropertyDeclarationNode)))
            {
                if (n.GetIsFunc) propertyMethods.Add(n.GetVal);
                if (n.SetIsFunc) propertyMethods.Add(n.SetVal);
            }

            // Search for all methods with multiple definitions.
            Dictionary<string, int> tempDefDict = new Dictionary<string, int>();
            foreach (ClassFunctionDeclarationNode n in this.GetNodesOfType(nodes, typeof(ClassFunctionDeclarationNode)))
            {
                if (n.AllKeywords.Contains("bound"))
                {
                    if (tempDefDict.Keys.Contains(n.Name))
                        tempDefDict[n.Name] += 1;
                    else
                        tempDefDict.Add(n.Name, 1);
                }
            }
            List<string> duplicates = new List<string>();
            foreach (string k in tempDefDict.Keys)
            {
                if (tempDefDict[k] > 1)
                    duplicates.Add(k);
            }

            // Declare the methods that are in the class.
            this.WriteHeaderLine("        /* Method and constructor declarations */");

            List<string> fdecls = new List<string>();
            foreach (ClassFunctionDeclarationNode n in this.GetNodesOfType(nodes, typeof(ClassFunctionDeclarationNode)))
            {
                string keys = "";
                string visibility = "private";
                string functype = n.Type;
                string funcname = n.Name;
                bool unbound = false;
                foreach (string k in n.CPPKeywords)
                {
                    if (!Keywords.CPPTypeKeywords.Contains(k) && !Keywords.LuaTypeKeywords.Contains(k))
                    {
                        if (Keywords.CPPVisibilityKeywords.Contains(k))
                            visibility = k;
                        else
                            keys += k + " ";
                    }
                    else if (!n.AllKeywords.Contains("bound") && functype == "")
                        functype = k;
                }
                if (duplicates.Contains(funcname) && (n.AllKeywords.Contains("bound") || n.Name == clscomponents[clscomponents.Length - 1]))
                {
                    // Change the name to a unique identifier.
                    string args = "";
                    foreach (string a in n.Arguments)
                    {
                        if (a.Trim().Length > 0 && a.Trim().IndexOf(' ') != -1)
                        {
                            string _a = a.Trim().Substring(0, a.Trim().IndexOf(' '));
                            args += "_" + _a.Replace(":", "_");
                        }
                    }
                    funcname = "__" + n.Name + args;

                    // Ensure this function is no longer bounded (as we'll
                    // have an automatically created function which dispatches
                    // to the unique functions based on the arguments passed).
                    if (n.AllKeywords.Contains("bound"))
                        unbound = true;

                    // Ensure there is a default type of void, in case this
                    // function is a constructor.
                    if (functype == "")
                        functype = "void";
                }
                string decl = "";
                if ((n.AllKeywords.Contains("bound") && !unbound) || propertyMethods.Contains(n.Name) || duplicates.Contains(n.Name))
                {
                    if (funcname == clscomponents[clscomponents.Length - 1]) // Constructor
                        decl = visibility + ": " + keys + funcname + "(lua_State * L, bool byuser);";
                    else if (n.Name == clscomponents[clscomponents.Length - 1] && duplicates.Contains(n.Name)) // Dispatched Constructor
                        decl = visibility + ": " + keys + "void " + funcname + "(lua_State * L, bool byuser);";
                    else if (n.AllKeywords.Contains("bound") || propertyMethods.Contains(n.Name) || duplicates.Contains(n.Name))
                        decl = visibility + ": " + keys + "int " + funcname + "(lua_State * L);";
                }
                else
                {
                    string args = "";
                    foreach (string a in n.Arguments)
                        args += a + ", ";
                    if (args != "")
                        args = args.Substring(0, args.Length - 2);
                    Regex r = new Regex("\\[[^\\]]",RegexOptions.Multiline);
                    args = r.Replace(args, "= $1");
                    if (funcname == cls.ClassOnly)
                    {
                        if (keys.Trim().Length > 0) keys = keys.Trim() + " ";
                        decl = visibility + ": " + keys + funcname + "(" + args + ");";
                    }
                    else
                        decl = visibility + ": " + keys + functype + " " + funcname + "(" + args + ");";
                }
                if (!fdecls.Contains(decl))
                    this.WriteHeaderLine("        " + decl);
                fdecls.Add(decl);
            }
            this.WriteHeaderLine();

            // Declare any dispatchers for overloaded methods.
            this.WriteHeaderLine("        /* Automatic dispatchers for overloaded methods */");

            List<string> fddecls = new List<string>();
            foreach (ClassFunctionDeclarationNode n in this.GetNodesOfType(nodes, typeof(ClassFunctionDeclarationNode)))
            {
                if (duplicates.Contains(n.Name) && (n.AllKeywords.Contains("bound") || n.Name == clscomponents[clscomponents.Length - 1]))
                {
                    string keys = "";
                    string decl = "";
                    foreach (string k in n.CPPKeywords)
                    {
                        if (!Keywords.CPPTypeKeywords.Contains(k) && !Keywords.LuaTypeKeywords.Contains(k))
                        {
                            if (!Keywords.CPPVisibilityKeywords.Contains(k))
                                keys += k + " ";
                        }
                    }
                    if (n.Name == clscomponents[clscomponents.Length - 1]) // Constructor
                        decl = "public: " + keys + n.Name + "(lua_State * L, bool byuser);";
                    else
                        decl = "public: " + keys + "int " + n.Name + "(lua_State * L);";
                    if (!fddecls.Contains(decl))
                        this.WriteHeaderLine("        " + decl);
                    fddecls.Add(decl);
                }
            }
            this.WriteHeaderLine();

            // Declare automatically generated methods for setting / getting properties.
            this.WriteHeaderLine("        /* Automatic property getter-setter declarations */");
            foreach (ClassPropertyDeclarationNode n in this.GetNodesOfType(nodes, typeof(ClassPropertyDeclarationNode)))
            {
                if (n.GetVal != null && !n.GetIsFunc)
                {
                    this.WriteHeaderLine("        private: int __autobind_property_get_" + n.Name + "(lua_State * L);");
                }
                if (n.SetVal != null && !n.SetIsFunc)
                {
                    this.WriteHeaderLine("        private: int __autobind_property_set_" + n.Name + "(lua_State * L);");
                }
            }
            this.WriteHeaderLine();

            // Write the declarations of the Binding variables.
            this.WriteHeaderLine("        /* Binding variables */");
            this.WriteHeaderLine("        public: static const char *ClassName;");
            this.WriteHeaderLine("        public: static const char *Inherits;");
			this.WriteHeaderLine("        public: static const Bindings<" + cls.ClassOnly + ">::FunctionType Functions[];");
            this.WriteHeaderLine("        public: static const Bindings<" + cls.ClassOnly + ">::PropertyType Properties[];");
            this.WriteHeaderLine("        public: static int (__cdecl *Dispatcher)(lua_State * L);");

            // End the declaration of the class.
            this.WriteHeaderLine("    };");
            for (int i = 0; i < clscomponents.Length - 1; i++)
            {
                this.WriteHeaderLine("} ");
            }
            this.WriteHeaderLine();

            //this.WriteHeaderLine("#pragma message(\"DEFINED " + cls.Class + "...\")");
            //this.WriteHeaderLine();

            // Add the #endif.
            this.WriteHeaderLine("#endif");
        }

        public override void WriteCodeFile(List<Node> nodes)
        {
            this.ValidityCheck(nodes);

            this.WriteCodeLine(@"/*
 * This file was automatically generated by AutoBind.
 * Any changes made to this file will be lost when it
 * is next regenerated.
 */");
            this.WriteCodeLine();

            // Get the class node.
            ClassDefinitionNode cls = (ClassDefinitionNode)(this.GetNodesOfType(nodes, typeof(ClassDefinitionNode))[0]);
            this.WriteCodeLine("#include \"autobind/types.h\"");
            this.WriteCodeLine("#include \"autobind/binding/lua.h\"");
            this.WriteCodeLine("#include \"RObject.h\"");
            this.WriteCodeLine("#include \"" + ClassName.ResolveToHeaderFilename(cls.Class) + "\"");
            List<Node> imports = this.GetNodesOfType(nodes, typeof(ImportNode));
            foreach (Node n in imports)
            {
                if (n.Content != cls.Inheritance)
                    this.WriteCodeLine("#include \"" + ClassName.ResolveToHeaderFilename(n.Content.Trim()) + "\"");
            }
            foreach (Node n in this.GetNodesOfType(nodes, typeof(IncludeNode)))
            {
                this.WriteCodeLine("#include " + n.Content.Trim() + "");
            }

            // Search for all methods with multiple definitions.
            Dictionary<string, int> tempDefDict = new Dictionary<string, int>();
            foreach (ClassFunctionDeclarationNode n in this.GetNodesOfType(nodes, typeof(ClassFunctionDeclarationNode)))
            {
                if (n.AllKeywords.Contains("bound"))
                {
                    if (tempDefDict.Keys.Contains(n.Name))
                        tempDefDict[n.Name] += 1;
                    else
                        tempDefDict.Add(n.Name, 1);
                }
            }
            List<string> duplicates = new List<string>();
            foreach (string k in tempDefDict.Keys)
            {
                if (tempDefDict[k] > 1)
                    duplicates.Add(k);
            }

            // If there are any duplicates, we need to include ArgumentCountMismatchException.
            if (duplicates.Count > 0)
                this.WriteCodeLine("#include \"ArgumentCountMismatchException.h\"");
            this.WriteCodeLine();

            // All of the import declarations will have been made in the header file,
            // so they aren't needed here.

            // All of the using declarations will have been made in the header file,
            // so they aren't needed here.

            // Begin defining the class.
            string[] clscomponents = cls.Class.Split('.');
            for (int i = 0; i < clscomponents.Length; i++)
            {
                if (i < clscomponents.Length - 2)
                    this.WriteCode("namespace " + clscomponents[i] + " { ");
                else if (i == clscomponents.Length - 2)
                    this.WriteCode("namespace " + clscomponents[i]);
                else
                {
                    this.WriteCodeLine();
                    this.WriteCodeLine("{");
                }
            }

            // Search for all methods used by properties.
            List<string> propertyMethods = new List<string>();
            foreach (ClassPropertyDeclarationNode n in this.GetNodesOfType(nodes, typeof(ClassPropertyDeclarationNode)))
            {
                if (n.GetIsFunc) propertyMethods.Add(n.GetVal);
                if (n.SetIsFunc) propertyMethods.Add(n.SetVal);
            }

            // Declare all of the static variables which are assigned within the class.
            this.WriteCodeLine("    /* Variable assignments */");
            foreach (ClassVariableDeclarationNode n in this.GetNodesOfType(nodes, typeof(ClassVariableDeclarationNode)))
            {
                if (n.CPPKeywords.Contains("static"))
                {
                    string keys = "";
                    foreach (string k in n.CPPKeywords)
                    {
                        if (!Keywords.CPPVisibilityKeywords.Contains(k) && !Keywords.CPPStripKeywords.Contains(k))
                            keys += k + " ";
                    }
                    if (n.Assign == "")
                        this.WriteCodeLine("    " + keys + n.Type + " " + cls.ClassOnly + "::" + n.Name + ";");
                    else
                        this.WriteCodeLine("    " + keys + n.Type + " " + cls.ClassOnly + "::" + n.Name + " = " + n.Assign + ";");
                }
            }
            this.WriteCodeLine();

            // Declare the methods that are in the class.
            this.WriteCodeLine("    /* Method and constructor definitions */");

            List<string> fdefs = new List<string>();
            bool funccontent = false;
            bool funcbound = false;
            string functype = "";
            string funcname = "";
            bool unbound = false;
            foreach (Node r in nodes)
            {
                if (r is ClassFunctionDeclarationNode)
                {
                    //this.WriteCodeLine("#line " + r.LineNumber + " \"" + r.FileName.Replace("\\", "\\\\") + "\"");
                    ClassFunctionDeclarationNode n = (ClassFunctionDeclarationNode)r;
                    string keys = "";
                    functype = n.Type;
                    funcname = n.Name;
                    unbound = false;
                    foreach (string k in n.CPPKeywords)
                    {
                        if (!Keywords.CPPVisibilityKeywords.Contains(k) && !Keywords.CPPStripKeywords.Contains(k))
                        {
                            if (!Keywords.CPPTypeKeywords.Contains(k) && !Keywords.LuaTypeKeywords.Contains(k))
                                keys += k + " ";
                            if (Keywords.CPPTypeKeywords.Contains(k) || Keywords.LuaTypeKeywords.Contains(k))
                            {
                                if (functype == "")
                                    functype = k;
                            }
                        }
                    }
                    if (duplicates.Contains(funcname) && (n.AllKeywords.Contains("bound") || n.Name == clscomponents[clscomponents.Length - 1]))
                    {
                        // Change the name to a unique identifier.
                        string args = "";
                        foreach (string a in n.Arguments)
                        {
                            if (a.Trim().Length > 0 && a.Trim().IndexOf(' ') != -1)
                            {
                                string _a = a.Trim().Substring(0, a.Trim().IndexOf(' '));
                                args += "_" + _a.Replace(":", "_");
                            }
                        }
                        funcname = "__" + n.Name + args;

                        // Ensure this function is no longer bounded (as we'll
                        // have an automatically created function which dispatches
                        // to the unique functions based on the arguments passed).
                        if (n.AllKeywords.Contains("bound"))
                            unbound = true;

                        // Ensure there is a default type of void, in case this
                        // function is a constructor.
                        if (functype == "")
                            functype = "void";
                    }
                    string def = "";
                    if ((n.AllKeywords.Contains("bound") && !unbound) || propertyMethods.Contains(n.Name) || duplicates.Contains(n.Name))
                    {
                        if (funcname == clscomponents[clscomponents.Length - 1]) // Constructor
                            def = keys + cls.ClassOnly + "::" + funcname + "(lua_State * L, bool byuser)";
                        else if (n.Name == clscomponents[clscomponents.Length - 1] && duplicates.Contains(n.Name)) // Dispatched Constructor
                            def = keys + "void " + cls.ClassOnly + "::" + funcname + "(lua_State * L, bool byuser)";
                        else
                            def = keys + "int " + cls.ClassOnly + "::" + funcname + "(lua_State * L)";
                    }
                    else
                    {
                        string args = "";
                        foreach (string a in n.Arguments)
                            args += a + ", ";
                        if (args != "")
                            args = args.Substring(0, args.Length - 2);
                        Regex re = new Regex("\\[[^\\]]", RegexOptions.Multiline);
                        args = re.Replace(args, "");
                        if (funcname == cls.ClassOnly)
                        {
                            if (keys.Trim().Length > 0) keys = keys.Trim() + " ";
                            def = keys + cls.ClassOnly + "::" + funcname + "(" + args + ")";
                        }
                        else
                            def = keys + functype + " " + cls.ClassOnly + "::" + funcname + "(" + args + ")";
                    }
                    if (!fdefs.Contains(def))
                    {
                        this.WriteCodeLine("    " + def);
                        this.WriteCodeLine("    {");
                        
                        // Add the argument bindings.
                        if ((n.AllKeywords.Contains("bound") && !unbound) || propertyMethods.Contains(n.Name) || duplicates.Contains(n.Name))
                        {
                            int ai = 1;
                            if (propertyMethods.Contains(n.Name)) ai = -1;

                            foreach (string a in n.Arguments)
                            {
                                Regex ar = new Regex("(?<Type>[a-zA-Z0-9_\\.\\:]+)[ \r\n\t]+(?<Name>[a-zA-Z0-9_\\.]+)([ \r\n\t]+\\[(?<Default>[^\\]]+)\\])?");
                                Match m = ar.Match(a);
                                if (m.Success)
                                {
                                    string type = m.Groups["Type"].Value;
                                    string name = m.Groups["Name"].Value;
                                    string defv = "";
                                    if (m.Groups["Default"].Success)
                                        defv = ", " + m.Groups["Default"].Value;

                                    if (Keywords.LuaTypeKeywords.Contains(type) || type == "bool")
                                        this.WriteCodeLine("        " + type + " " + name + " = Bindings<" + type + ">::GetArgumentBase(L, " + ai + "" + defv + ");");
                                    else
                                        this.WriteCodeLine("        " + type + " * " + name + " = Bindings<" + type + ">::GetArgument(L, " + ai + "" + defv + ");");
                                    if (!propertyMethods.Contains(n.Name))
                                        ai += 1;
                                }
                            }
                            if (ai != 1) this.WriteCodeLine();
                        }
                        funcbound = ((n.AllKeywords.Contains("bound") && !unbound) || propertyMethods.Contains(n.Name) || duplicates.Contains(n.Name));
                        funccontent = true;
                    }
                    fdefs.Add(def);
                }
                else if (r is DirectNode && funccontent)
                {
                    // Search the content for any return statements, and use a Regex to
                    // replace them with the appropriate bindings.
                    if (funcbound)
                    {
                        string res = null;
                        if (functype != "variant")
                        {
                            Regex rr = new Regex("^([ \r\n\t]*)([^\\/][^\\/])([^/\n]*)([ \r\n\t]+)return[ \r\n\t]+(?<Val>[^\\;]+)\\;", RegexOptions.Multiline);
                            res = rr.Replace(r.Content, (match) =>
                                {
                                    if (match.Groups["Val"].Value.StartsWith("Bindings<"))
                                        return match.Value;
                                    else
                                        return String.Format(
                                            "{0}{1}{2}{3}return Bindings<" + functype + ">::Result(L, {4});",
                                            match.Groups[1].Value,
                                            match.Groups[2].Value,
                                            match.Groups[3].Value,
                                            match.Groups[4].Value,
                                            match.Groups["Val"].Value
                                        );
                                }
                            );
 
                            Regex brr = new Regex("([ \r\n\t]*)([^\\/][^\\/])([^/\n]*)([ \r\n\t]+)return[ \r\n\t]*\\;", RegexOptions.Multiline);
                            res = brr.Replace(res, "${1}${2}${3}${4}return Bindings<void*>::EmptyResult;");
                        }
                        else
                            res = r.Content;
                        this.WriteCodeLine("        " + res.TrimStart().TrimEnd('\n'));
                        string cc = res.TrimStart().TrimEnd('\n');
                        if (cc.LastIndexOf("///") != -1 && !cc.Substring(cc.LastIndexOf("///"), 0).Contains('\n'))
                        {
                            // There's a doc-comment on the last line, so we don't want
                            // to call this.WriteCodeLine() as it will seperate it from
                            // the function definition.
                        }
                        else
                            this.WriteCodeLine();
                    }
                    else
                    {
                        this.WriteCodeLine("        " + r.Content.TrimStart().TrimEnd('\n'));
                        string cc = r.Content.TrimStart().TrimEnd('\n');
                        if (cc.LastIndexOf("///") != -1 && !cc.Substring(cc.LastIndexOf("///"), 0).Contains('\n'))
                        {
                            // There's a doc-comment on the last line, so we don't want
                            // to call this.WriteCodeLine() as it will seperate it from
                            // the function definition.
                        }
                        else
                            this.WriteCodeLine();
                    }
                    funcbound = false;
                    funccontent = false;
                }
            }

            // Declare any dispatchers for overloaded methods.
            this.WriteCodeLine("    /* Automatic dispatchers for overloaded methods */");
            foreach (string d in duplicates)
            {
                if (d == clscomponents[clscomponents.Length - 1])
                {
                    this.WriteCodeLine("    " + cls.ClassOnly + "::" + d + "(lua_State * L, bool byuser)");
                    this.WriteCodeLine("    {");
                    string elsestmt = "";
                    foreach (ClassFunctionDeclarationNode n in this.GetNodesOfType(nodes, typeof(ClassFunctionDeclarationNode)))
                    {
                        if (n.Name != d)
                            continue;

                        // Change the name to a unique identifier.
                        string functname = n.Name;
                        string args = "";
                        foreach (string a in n.Arguments)
                        {
                            if (a.Trim().Length > 0 && a.Trim().IndexOf(' ') != -1)
                            {
                                string _a = a.Trim().Substring(0, a.Trim().IndexOf(' '));
                                args += "_" + _a.Replace(":", "_");
                            }
                        }
                        functname = "__" + n.Name + args;

                        if (n.Arguments.Count == 0 || n.Arguments[0] == "")
                        {
                            this.WriteCodeLine("        " + elsestmt + "if (lua_gettop(L) == 0 || !byuser)");
                            this.WriteCodeLine("        {");
                            this.WriteCodeLine("            this->" + functname + "(L, byuser);");
                            this.WriteCodeLine("        }");
                        }
                        else
                        {
                            this.WriteCodeLine("        " + elsestmt + "if (lua_gettop(L) == " + n.Arguments.Count + " &&");
                            int ai = 1;
                            foreach (string a in n.Arguments)
                            {
                                string t = a.Trim().Substring(0, a.Trim().IndexOf(' '));
                                string andd = " &&";
                                if (ai == n.Arguments.Count) andd = ")";
                                if (Keywords.LuaTypeKeywords.Contains(t) || t == "bool")
                                    this.WriteCodeLine("            Bindings<" + t + ">::IsArgumentBase(L, " + ai + ")" + andd);
                                else
                                    this.WriteCodeLine("            Bindings<" + t + ">::IsArgument(L, " + ai + ")" + andd);
                                ai += 1;
                            }
                            this.WriteCodeLine("        {");
                            this.WriteCodeLine("            this->" + functname + "(L, byuser);");
                            this.WriteCodeLine("        }");
                        }
                        if (elsestmt == "") elsestmt = "else ";
                    }
                    this.WriteCodeLine("        else");
                    this.WriteCodeLine("            throw new Engine::ArgumentCountMismatchException();");
                    this.WriteCodeLine("    }");
                    this.WriteCodeLine();
                }
                else
                {
                    this.WriteCodeLine("    int " + cls.ClassOnly + "::" + d + "(lua_State * L)");
                    this.WriteCodeLine("    {");
                    string elsestmt = "";
                    foreach (ClassFunctionDeclarationNode n in this.GetNodesOfType(nodes, typeof(ClassFunctionDeclarationNode)))
                    {
                        if (n.Name != d)
                            continue;

                        // Change the name to a unique identifier.
                        string functname = n.Name;
                        string args = "";
                        foreach (string a in n.Arguments)
                        {
                            if (a.Trim().Length > 0 && a.Trim().IndexOf(' ') != -1)
                            {
                                string _a = a.Trim().Substring(0, a.Trim().IndexOf(' '));
                                args += "_" + _a.Replace(":", "_");
                            }
                        }
                        functname = "__" + n.Name + args;

                        if (n.Arguments.Count == 0 || n.Arguments[0] == "")
                        {
                            this.WriteCodeLine("        " + elsestmt + "if (lua_gettop(L) == 0)");
                            this.WriteCodeLine("        {");
                            this.WriteCodeLine("            return this->" + functname + "(L);");
                            this.WriteCodeLine("        }");
                        }
                        else
                        {
                            this.WriteCodeLine("        " + elsestmt + "if (lua_gettop(L) == " + n.Arguments.Count + " &&");
                            int ai = 1;
                            foreach (string a in n.Arguments)
                            {
                                string t = a.Trim().Substring(0, a.Trim().IndexOf(' '));
                                string andd = " &&";
                                if (ai == n.Arguments.Count) andd = ")";
                                if (Keywords.LuaTypeKeywords.Contains(t) || t == "bool")
                                    this.WriteCodeLine("            Bindings<" + t + ">::IsArgumentBase(L, " + ai + ")" + andd);
                                else
                                    this.WriteCodeLine("            Bindings<" + t + ">::IsArgument(L, " + ai + ")" + andd);
                                ai += 1;
                            }
                            this.WriteCodeLine("        {");
                            this.WriteCodeLine("            return this->" + functname + "(L);");
                            this.WriteCodeLine("        }");
                        }
                        if (elsestmt == "") elsestmt = "else ";
                    }
                    this.WriteCodeLine("        else");
                    this.WriteCodeLine("            throw new Engine::ArgumentCountMismatchException();");
                    this.WriteCodeLine("    }");
                    this.WriteCodeLine();
                }
            }
            this.WriteHeaderLine();

            // Declare automatically generated methods for setting / getting properties.
            this.WriteCodeLine("    /* Automatic property getter-setter definitions */");
            foreach (ClassPropertyDeclarationNode n in this.GetNodesOfType(nodes, typeof(ClassPropertyDeclarationNode)))
            {
                if (n.GetVal != null && !n.GetIsFunc)
                {
                    this.WriteCodeLine("    int " + cls.ClassOnly + "::__autobind_property_get_" + n.Name + "(lua_State * L)");
                    this.WriteCodeLine("    {");
                    this.WriteCodeLine("        return Bindings<numeric>::Result(L, " + n.GetVal + ");");
                    this.WriteCodeLine("    }");
                    this.WriteCodeLine();
                }
                if (n.SetVal != null && !n.SetIsFunc)
                {
                    this.WriteCodeLine("    int " + cls.ClassOnly + "::__autobind_property_set_" + n.Name + "(lua_State * L)");
                    this.WriteCodeLine("    {");
                    this.WriteCodeLine("        " + n.SetVal + " = Bindings<numeric>::GetArgument(L, -1);");
                    this.WriteCodeLine("        return Bindings<numeric>::EmptyResult;");
                    this.WriteCodeLine("    }");
                    this.WriteCodeLine();
                }
            }

            // Write the definitions of the Binding variables.
            this.WriteCodeLine("    /* Binding variables */");
            if (cls.Alias != "")
                this.WriteCodeLine("    const char* " + cls.ClassOnly + "::ClassName = \"" + cls.Alias + "\";");
            else
                this.WriteCodeLine("    const char* " + cls.ClassOnly + "::ClassName = \"" + cls.Class + "\";");
            this.WriteCodeLine("    const char* " + cls.ClassOnly + "::Inherits = \"" + cls.Inheritance + "\";");
            this.WriteCodeLine("    const Bindings<" + cls.ClassOnly + ">::FunctionType " + cls.ClassOnly + "::Functions[] =");
            this.WriteCodeLine("    {");
            foreach (ClassFunctionDeclarationNode n in this.GetNodesOfType(nodes, typeof(ClassFunctionDeclarationNode)))
            {
                if (n.AllKeywords.Contains("bound") && n.Name != clscomponents[clscomponents.Length - 1])
                    this.WriteCodeLine("        {\"" + n.Name + "\", &" + cls.ClassOnly + "::" + n.Name + "},");
            }
            this.WriteCodeLine("        {0}");
            this.WriteCodeLine("    };");
            this.WriteCodeLine("    const Bindings<" + cls.ClassOnly + ">::PropertyType " + cls.ClassOnly + "::Properties[] =");
            this.WriteCodeLine("    {");
            foreach (ClassPropertyDeclarationNode n in this.GetNodesOfType(nodes, typeof(ClassPropertyDeclarationNode)))
            {
                if (n.AllKeywords.Contains("bound"))
                {
                    string setfunc = "";
                    string getfunc = "";
                    if (n.SetIsFunc)
                        setfunc = n.SetVal;
                    else
                        setfunc = "__autobind_property_set_" + n.Name;
                    if (n.GetIsFunc)
                        getfunc = n.GetVal;
                    else
                        getfunc = "__autobind_property_get_" + n.Name;
                    if (setfunc == null) // Check for read-only properties.
                        this.WriteCodeLine("        {\"" + n.Name + "\", &" + cls.ClassOnly + "::" + getfunc + ", NULL},");
                    else
                        this.WriteCodeLine("        {\"" + n.Name + "\", &" + cls.ClassOnly + "::" + getfunc + ", &" + cls.ClassOnly + "::" + setfunc + "},");
                }
            }
            this.WriteCodeLine("        {0}");
            this.WriteCodeLine("    };");
            this.WriteCodeLine("    int (__cdecl *" + cls.ClassOnly + "::Dispatcher)(lua_State * L) = &(Bindings<" + cls.ClassOnly + ">::FunctionDispatch);");

            // End the definition of the class.
            for (int i = 0; i < clscomponents.Length - 1; i++)
            {
                this.WriteCodeLine("} ");
            }
            this.WriteCodeLine();
        }

        private void WriteHeader(string str)
        {
            str = str.Replace("\r\n", "\n");
            this.m_HeaderLineNumber += Regex.Matches(str, "\n").Count;
            this.m_M.OutputHFile.Write(str);
        }

        private void WriteHeaderLine(string str)
        {
            str = str.Replace("\r\n", "\n");
            this.m_HeaderLineNumber += Regex.Matches(str, "\n").Count + 1;
            this.m_M.OutputHFile.WriteLine(str);
        }

        private void WriteHeaderLine()
        {
            this.m_HeaderLineNumber += 1;
            this.m_M.OutputHFile.WriteLine();
        }

        private void WriteCode(string str)
        {
            str = str.Replace("\r\n", "\n");
            this.m_CodeLineNumber += Regex.Matches(str, "\n").Count;
            this.m_M.OutputCFile.Write(str);
        }

        private void WriteCodeLine(string str)
        {
            str = str.Replace("\r\n", "\n");
            this.m_CodeLineNumber += Regex.Matches(str, "\n").Count + 1;
            this.m_M.OutputCFile.WriteLine(str);
        }

        private void WriteCodeLine()
        {
            this.m_CodeLineNumber += 1;
            this.m_M.OutputCFile.WriteLine();
        }

        private void ValidityCheck(List<Node> nodes)
        {
            int classCount = 0;
            foreach (Node n in nodes)
            {
                if (n is ClassDefinitionNode)
                    classCount += 1;
            }

            if (classCount != 1)
                throw new InvalidClassDefinitionException("There must only be one class defined within a AutoBind source file.");
        }

        private List<Node> GetNodesOfType(List<Node> nodes, Type t)
        {
            List<Node> ret = new List<Node>();
            foreach (Node n in nodes)
            {
                if (n.GetType() == t)
                    ret.Add(n);
            }
            return ret;
        }
    }
}
